package com.emeraldparser.util;

import static com.google.common.collect.Collections2.filter;
import static com.google.common.collect.Collections2.transform;

import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multiset;

/**
 * 
 * @author Adam Paynter
 *
 * @param <E> The type of element in the collection
 * @param <K> The type of key in the map
 * @param <V> The type of value in the map
 */
public abstract class CollectionBasedMultimap<E, K, V> implements Multimap<K, V> {

	protected abstract Collection<E> collection();

	protected abstract K key(E element);

	protected abstract V value(E element);

	protected abstract E element(K key, V value);

	private Function<E, K> keyFunction() {
		return new Function<E, K>() {
			@Override
			public K apply(E element) {
				return key(element);
			}
		};
	}

	private Function<E, V> valueFunction() {
		return new Function<E, V>() {
			@Override
			public V apply(E element) {
				return value(element);
			}
		};
	}

	@Override
	public int size() {
		return collection().size();
	}

	@Override
	public boolean isEmpty() {
		return collection().isEmpty();
	}

	@Override
	public boolean containsKey(Object key) {
		return transform(collection(), keyFunction()).contains(key);
	}

	@Override
	public boolean containsValue(Object value) {
		return transform(collection(), valueFunction()).contains(value);
	}

	@Override
	public boolean containsEntry(final Object key, final Object value) {
		return Iterables.any(
				collection(),
				new Predicate<E>() {
					@Override
					public boolean apply(E element) {
						return Objects.equal(key, key(element))
						    && Objects.equal(value, value(element));
					}
				}
			);
	}

	@Override
	public boolean put(K key, V value) {
		return collection().add(element(key, value));
	}

	@Override
	public boolean remove(final Object key, final Object value) {
		// TODO Auto-generated method stub
		return Iterables.removeIf(
				collection(),
				new Predicate<E>() {
					@Override
					public boolean apply(E element) {
						return Objects.equal(key, key(element))
						    && Objects.equal(value, value(element));
					}
				}
			);
	}

	@Override
	public boolean putAll(K key, Iterable<? extends V> values) {
		// TODO Auto-generated method stub
		boolean modified = false;
		
		for (V value : values) {
			modified |= put(key, value);
		}
		
		return modified;
	}

	@Override
	public boolean putAll(Multimap<? extends K, ? extends V> multimap) {
		// TODO Auto-generated method stub
		boolean modified = false;
		
		for (Entry<? extends K, ? extends V> entry : multimap.entries()) {
			modified |= put(entry.getKey(), entry.getValue());
		}
		
		return modified;
	}

	@Override
	public Collection<V> replaceValues(final K key, Iterable<? extends V> values) {
		Collection<V> oldValues = removeAll(key);
		
		Iterables.addAll(
				collection(),
				Iterables.transform(
					values,
					new Function<V, E>() {
						@Override
						public E apply(V value) {
							return element(key, value);
						}
					}
				)
			);
		
		return oldValues;
	}

	@Override
	public Collection<V> removeAll(Object key) {
		// TODO Auto-generated method stub
		Collection<V> oldValues = HashMultiset.create();
		
		Iterator<V> iterator = transform(filter(collection(), keyPredicate(key)), valueFunction()).iterator();
		while (iterator.hasNext()) {
			oldValues.add(iterator.next());
			iterator.remove();
		}
		
		return oldValues;
	}

	@Override
	public void clear() {
		collection().clear();
	}

	@Override
	public Collection<V> get(K key) {
		return transform(filter(collection(), keyPredicate(key)), valueFunction());
	}

	private Predicate<E> keyPredicate(final Object key) {
		return new Predicate<E>() {
			@Override
			public boolean apply(E element) {
				return Objects.equal(key, key(element));
			}
		};
	}

	@Override
	public Set<K> keySet() {
		return keys().elementSet();
	}

	@Override
	public Multiset<K> keys() {
		return new CollectionBasedMultiset<K>() {

			@Override
			protected Collection<K> collection() {
				return transform(CollectionBasedMultimap.this.collection(), keyFunction());
			}

		};
	}

	@Override
	public Collection<V> values() {
		return transform(collection(), valueFunction());
	}

	@Override
	public Collection<Entry<K, V>> entries() {
		// TODO Auto-generated method stub
		return transform(
				collection(),
				new Function<E, Entry<K, V>>() {
					@Override
					public Entry<K, V> apply(E element) {
						return ImmutableMapEntry.create(key(element), value(element));
					}
				}
			);
	}

	@Override
	public Map<K, Collection<V>> asMap() {
		// TODO Auto-generated method stub
		return new CollectionBasedMap<E, K, Collection<V>>() {

			@Override
			protected Collection<E> collection() {
				return CollectionBasedMultimap.this.collection();
			}

			@Override
			protected K key(E element) {
				return CollectionBasedMultimap.this.key(element);
			}

			@Override
			protected Collection<V> value(Object key, Collection<E> filteredElements) {
				// TODO Auto-generated method stub
				return transform(filteredElements, valueFunction());
			}

			@Override
			protected Iterable<? extends E> elements(final K key, Collection<V> values) {
				return transform(
						values,
						new Function<V, E>() {
							@Override
							public E apply(V value) {
								return element(key, value);
							}
						}
					);
			}

		};
	}

	@Override
	public int hashCode() {
		return asMap().hashCode();
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == this) {
			return true;
		}
		
		if (obj == null) {
			return false;
		}
		
		if (!(obj instanceof Multimap<?, ?>)) {
			return false;
		}
		
		Multimap<?, ?> other = (Multimap<?, ?>) obj;
		
		return asMap().equals(other.asMap());
	}

	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		
		builder.append("{");
		
		String keyDelimiter = "";
		for (Map.Entry<K, Collection<V>> entry : asMap().entrySet()) {
			builder.append(keyDelimiter).append(entry.getKey());
			builder.append("[");
			
			String valueDelimiter = "";
			for (V value : entry.getValue()) {
				builder.append(valueDelimiter).append(value);
				valueDelimiter = ", ";
			}
			
			builder.append("]");
		}
		
		builder.append("}");
		
		return builder.toString();
	}

}
